package service

import (
	"bufio"
	"bytes"
	"context"
	"errors"
	"exporter/dao"
	"exporter/models"
	"exporter/setting"
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"
)

var metricStr string = `
# TYPE ${gauge} gauge
# HELP ${gauge} for process ${Name} active monitor.
${gauge}{label="${ProcName}",node="${IP}"} ${value}
          `

func CheckCmdStatus(pid int) int {
	if pid < 1 {
		return 0
	}
	ctx, cancel := context.WithCancel(context.Background())
	go func(cancelFunc context.CancelFunc) {
		time.Sleep(3 * time.Second)
		cancelFunc()
	}(cancel)
	str := InvokeCommand(ctx, "ps -ef|awk '{print $2}'|grep "+strconv.Itoa(pid)+"|wc -l")
	iStr, _ := strconv.Atoi(str)
	return iStr
}

func CheckCmdStatusWithStartCMD(content string) int {
	ctx, cancel := context.WithCancel(context.Background())
	go func(cancelFunc context.CancelFunc) {
		time.Sleep(3 * time.Second)
		cancelFunc()
	}(cancel)
	content = strings.Trim(strings.ReplaceAll(content, "&", ""), " ")
	str := InvokeCommand(ctx, "ps -ef|grep '"+content+"'|grep -v grep|wc -l")
	iStr, _ := strconv.Atoi(str)
	return iStr
}

func InvokeCommand(ctx context.Context, strCmd string) string {
	c := exec.CommandContext(ctx, "bash", "-c", strCmd)
	stdout, err := c.StdoutPipe()
	c.Start()
	content, err := ioutil.ReadAll(stdout)
	if err != nil {
		fmt.Println(err)
	}
	str := string(content)
	//fmt.Println(str)
	proState, err := c.Process.Wait()
	if err != nil {
		panic(err)
	}
	fmt.Println(proState.String())
	str = strings.Replace(str, " ", "", -1)
	// 去除换行符
	str = strings.Replace(str, "\n", "", -1)
	return str
}

func read(ctx context.Context, wg *sync.WaitGroup, std io.ReadCloser, logfile *os.File) {
	reader := bufio.NewReader(std)
	defer wg.Done()
	for {
		select {
		case <-ctx.Done():
			return
		default:
			readString, err := reader.ReadString('\n')

			if err != nil || err == io.EOF {
				return
			}
			fmt.Print(readString)
			logfile.WriteString(readString)
		}
	}
}

func StartCommand(ctx context.Context, prc models.Process) error {
	fmt.Println("-------StartCommand------")
	if GetProcessStatus(prc) {
		fmt.Println("pid:", prc.Pid, "-------已启动------")
		return nil
	}
	//c := exec.CommandContext(ctx, "bash", "-c", "nohup "+prc.Command+" >/dev/null 2>&1 &") // mac linux
	c := exec.CommandContext(ctx, "bash", "-c", prc.Command) // mac linux

	stdout, err := c.StdoutPipe()
	if err != nil {
		return err
	}
	stderr, err := c.StderrPipe()
	if err != nil {
		return err
	}
	logfile := fmt.Sprintf("%s%s_%s.log", "/tmp/", strings.ReplaceAll(prc.Name, " ", ""), time.Now().Format("2006_01_02"))
	var wg sync.WaitGroup
	// 因为有2个任务, 一个需要读取stderr 另一个需要读取stdout
	wg.Add(2)

	file, err := os.OpenFile(logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
	if err != nil {
		fmt.Println("open file failed, err:", err)
		return err
	}
	defer file.Close()

	go read(ctx, &wg, stderr, file)
	go read(ctx, &wg, stdout, file)
	// 这里一定要用start,而不是run 详情请看下面的图
	err = c.Start()
	fmt.Println("----err------", err, prc.Command)
	fmt.Println("pid-->", c.Process.Pid)
	prc.Pid = c.Process.Pid
	prc.UpdateTime = time.Now()
	prc.SysLogFile = logfile
	//time.Sleep(time.Second)
	//time.Sleep(100*time.Microsecond)
	//status := CheckCmdStatus(prc.Pid)
	//fmt.Printf("check status is %d\n",status)
	//if status >= 1 {
	prc.Status = true
	//}
	models.UpdateAProcess(&prc)
	proState, err := c.Process.Wait()
	if err != nil {
		panic(err)
	}
	fmt.Println("waiting status-->", proState.String(), proState.ExitCode(), proState.Exited(), proState.Success())
	if proState.ExitCode() == 0 {
		//通知任务结束
		fmt.Println("exit 0-------")
	}
	// 等待任务结束
	wg.Wait()
	fmt.Println("waiting completed--logfile-----")
	prc.Status = false
	prc.UpdateTime = time.Now()

	//prc.Pid = -1
	models.UpdateAProcess(&prc)
	fmt.Println(prc.Pid, "----end------", prc.Command)
	return err
}

func StopCommand(ctx context.Context, prc models.Process) error {
	fmt.Println("-------StopCommand------")
	c := exec.CommandContext(ctx, "bash", "-c", prc.ShutdownCmd) // mac linux

	stdout, err := c.StdoutPipe()
	if err != nil {
		return err
	}
	stderr, err := c.StderrPipe()
	if err != nil {
		return err
	}
	var wg sync.WaitGroup
	// 因为有2个任务, 一个需要读取stderr 另一个需要读取stdout
	wg.Add(2)

	file, err := os.OpenFile(prc.SysLogFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
	if err != nil {
		fmt.Println("open file failed, err:", err)
		return err
	}
	defer file.Close()

	go read(ctx, &wg, stderr, file)
	go read(ctx, &wg, stdout, file)
	// 这里一定要用start,而不是run 详情请看下面的图
	err = c.Start()
	fmt.Println("----err------", err, prc.ShutdownCmd)
	fmt.Println("pid-->", c.Process.Pid)
	prc.Pid = c.Process.Pid
	prc.UpdateTime = time.Now()
	time.Sleep(time.Microsecond)

	models.UpdateAProcess(&prc)
	proState, err := c.Process.Wait()
	if err != nil {
		panic(err)
	}
	fmt.Println(proState.String())
	// 等待任务结束
	wg.Wait()

	fmt.Println("----end------", prc.ShutdownCmd)
	return err
}

func ShutdownCommand(prc models.Process) error {
	//c := exec.CommandContext(ctx, "bash", "-c", prc.Command) // mac linux
	fmt.Println("prc.Pid==>", prc.Pid)
	if prc.Pid == -1 || prc.Pid == 0 {
		prc.Status = false
		models.UpdateAProcess(&prc)
		return errors.New("no process found")
	}
	var err error
	fmt.Println("shutCommand-->", prc.ShutdownCmd)
	if prc.ShutdownCmd != "" {
		cmd := exec.Command("bash", "-c", prc.ShutdownCmd)
		cmd.Stdout = os.Stdout
		err = cmd.Run()
	} else {
		err = syscall.Kill(prc.Pid, syscall.SIGKILL)
	}

	prc.UpdateTime = time.Now()
	time.Sleep(100 * time.Microsecond)
	status := CheckCmdStatus(prc.Pid)
	fmt.Printf("check status is %d\n", status)
	if status <= 0 {
		prc.Status = false
		//prc.Pid = -1
	}
	models.UpdateAProcess(&prc)
	//ps -ef|awk '{print $2}'|grep 15|wc -l
	return err
}

func GetMetrics() (content string, err error) {
	var prcList []models.Process
	if err = dao.DB.Find(&prcList).Error; err != nil {
		return "", err
	}
	exporterName := os.Getenv("EXPORTER_NAME")
	if exporterName == "" {
		exporterName = setting.Conf.Name
	}
	var buffer bytes.Buffer
	for _, p := range prcList {
		status := CheckCmdStatus(p.Pid)
		if status == 0 {
			status = CheckCmdStatusWithStartCMD(p.Command)
		}
		processName := strings.ReplaceAll(p.Name, " ", "_")
		gauge := exporterName
		str := strings.ReplaceAll(metricStr, "${gauge}", gauge)
		str = strings.ReplaceAll(str, "${Name}", p.Name)
		str = strings.ReplaceAll(str, "${ProcName}", processName)
		if p.IP == "" {
			str = strings.ReplaceAll(str, "${IP}", "localhost")
		} else {
			str = strings.ReplaceAll(str, "${IP}", p.IP)
		}
		str = strings.ReplaceAll(str, "${value}", strconv.Itoa(status))
		buffer.WriteString(str)
		//buffer.WriteString("\n")
		var statusFlag = false
		if status == 1 {
			statusFlag = true
		}
		if p.Status != statusFlag {
			if !statusFlag {
				p.Pid = -1
			}
			models.UpdateAProcess(&p)
		}
	}

	return buffer.String(), nil
}

func ViewLogs(ctx *gin.Context, prc models.Process) error {
	fmt.Println("-------View Logs------")

	client, err := NewClient(ctx.Writer, ctx.Request, nil)
	if err != nil {
		fmt.Println(err, "upgrade websocket failed")
		ctx.String(http.StatusBadRequest, err.Error())
		return err
	}

	if prc.SysLogFile == "" {
		fmt.Println("pid:", prc.Pid, "-------没有写系统日志文件------")
		return errors.New("没有写系统日志文件")
	}

	ReadLogs(ctx, prc.SysLogFile, client)

	return err
}

func SetProcessListStatus(prcList []*models.Process) {
	for _, p := range prcList {
		status := CheckCmdStatus(p.Pid)
		if status == 0 {
			status = CheckCmdStatusWithStartCMD(p.Command)
		}
		if status == 1 {
			p.Status = true
		} else {
			p.Status = false
		}
	}
}

func GetProcessStatus(p models.Process) bool {
	status := CheckCmdStatus(p.Pid)
	if status == 0 {
		status = CheckCmdStatusWithStartCMD(p.Command)
	}
	if status == 1 {
		return true
	} else {
		return false
	}

}
